package com.jandar.parser;

import java.io.File;
import java.sql.Connection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jandar.Resources;
import com.jandar.logging.Logger;
import com.jandar.logging.LoggerFactory;
import com.jandar.parser.exception.ParserException;
import com.jandar.parser.impl.TXTParser;
import com.jandar.parser.impl.XLSParser;
import com.jandar.parser.impl.XMLParser;
import com.jandar.table.config.IdGeneratorInfo;
import com.jandar.table.config.Table;
import com.jandar.util.DBUtils;
import com.jandar.util.DateUtils;
import com.jandar.util.FileUtils;

/**
 * 解析器基类。
 * 
 * @author pqj
 * @version 2009-6-2
 */
public abstract class BaseParser implements IParser {

	public static final String XLS_TYPE = ".xls";

	public static final String XML_TYPE = ".xml";

	public static final String TXT_TYPE = ".txt";

	public static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";

	public static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
	
	public static final String FAILURERECORD_FILE_PATTERN = "[{table}]-[ERROR]-[{date}].txt";
	
	public static final String NEWLINE = System.getProperty("line.separator");

	protected static final String FILE_PATTERN = "^\\[(\\w+)\\].*";
	
	protected static volatile boolean cancelled = false;

	protected DBUtils db = DBUtils.getInstance();

	protected Logger logger = LoggerFactory.getInstance(this.getClass());

	private static Map<String, IParser> context = new ConcurrentHashMap<String, IParser>();
	
	private static final boolean hasEmptyField = Resources.getInstance().isHasEmptyField();

	/** 插入条数 */
	protected int insertCount = 0;

	/** 更新条数 */
	protected int updateCount = 0;

	/** 失败条数 */
	protected int failureCount = 0;

	/**
	 * 返回插入条数。
	 * 
	 * @return 插入条数
	 */
	public int getInsertCount() {
		return insertCount;
	}

	/**
	 * 返回更新条数。
	 * 
	 * @return 更新条数
	 */
	public int getUpdateCount() {
		return updateCount;
	}

	/**
	 * 返回失败条数。
	 * 
	 * @return 失败条数
	 */
	public int getFailureCount() {
		return failureCount;
	}

	/**
	 * 总条数。
	 * 
	 * @return 总条数
	 */
	public int getTotalCount() {
		return insertCount + updateCount + failureCount;
	}
	
	/**
	 * 返回交换条数。
	 * 
	 * @return 交换条数
	 */
	public int getChangeCount() {
		return insertCount + updateCount;
	}

	/**
	 * 创建一个解析器实例。
	 * 
	 * @param type 文件类型
	 * @return 解析器
	 */
	public static IParser getInstance(String type) {
		IParser parser = context.get(type);
		if (parser == null) {
			if (XLS_TYPE.equals(type)) {

				parser = new XLSParser();
				context.put(XLS_TYPE, parser);
			} else if (TXT_TYPE.equals(type)) {

				parser = new TXTParser();
				context.put(TXT_TYPE, parser);
			} else if (XML_TYPE.equals(type)) {

				parser = new XMLParser();
				context.put(XML_TYPE, parser);
			}
		}
		return parser;
	}
	
	/**
	 * 插入或者更新操作。 以最大限度处理文件中的记录，有出错的记录，写入日志。 统计插入条数、更新条数、失败条数。
	 * 
	 * @param conn 数据库连接
	 * @param file 指定文件
	 * @throws Exception 异常
	 */
	public void insertOrUpdate(Connection conn, File file) throws Exception {
		insertCount = 0;
		updateCount = 0;
		failureCount = 0;
	}

	/**
	 * 错误记录写入日志文件。放在error目录。文件名以[tableName]-[ERROR]-[date].txt格式命名。
	 * 
	 * @param failureFileName 错误记录文件名
	 * @param values 字段值
	 * @param length 长度
	 */
	public void writeFailureRecord(String failureFileName, String[] values, int length) {
		StringBuilder buf = new StringBuilder();
		int len = (values.length > length) ? length : values.length; 
		for (int i=0; i<len; ++i) {
			if (buf.length() > 0) {
				buf.append("\t");
			}
			buf.append(values[i]);
		}
		buf.append(NEWLINE);
		FileUtils.writeFile(failureFileName, buf.toString());
	}
	
	/**
	 * 错误记录写入日志文件。放在error目录。文件名以[tableName]-[ERROR]-[date].txt格式命名。
	 * 
	 * @param failureFileName 错误记录文件名
	 * @param line 字段值
	 */
	public void writeFailureRecord(String failureFileName, String line) {
		FileUtils.writeFile(failureFileName, line + NEWLINE);
	}

	/**
	 * 根据文件名取得表名。
	 * 
	 * @param file 文件
	 * @return 表名
	 * @throws Exception 异常
	 */
	public static String getTableName(File file) throws Exception {
		String ret = null;
		Pattern pattern = Pattern.compile(FILE_PATTERN);
		Matcher matcher = pattern.matcher(file.getName());
		if (matcher.find()) {
			ret = matcher.group(1);
		} else {
			throw new ParserException("文件：<" + file.getAbsolutePath() + ">" + "命名格式错误！例如：[tableName]XXXX.xls。");
		}
		return ret;
	}
	
	public static int getInputFileCols(Table table, IdGeneratorInfo generatorInfo) {
		int ret = 0;
		if (IdGeneratorInfo.AUTO == generatorInfo || IdGeneratorInfo.SEQUENCE == generatorInfo) {
			if (hasEmptyField) {
				ret = table.getAllColumnCount();
			} else {
				ret = table.getColumnCount();
			}

			// ID需要录入的时候，下标是一一对应的。
		} else {
			ret = table.getAllColumnCount();
		}
		return ret;
	}

	/**
	 * 返回录入字段所对应的数据库字段下标。
	 * 
	 * @param generatorInfo 主键生成策略
	 * @param idSize ID个数
	 * @param valuesIndex 录入字段下标
	 * @return 数据库字段下标
	 */
	protected int getColumnIndex(IdGeneratorInfo generatorInfo, int idSize, int valuesIndex) {
		int ret = 0;
		// ID没有录入的时候，下标需要补齐。
		if (IdGeneratorInfo.AUTO == generatorInfo || IdGeneratorInfo.SEQUENCE == generatorInfo) {
			if (hasEmptyField) {
				ret = valuesIndex;
			} else {
				ret = idSize + valuesIndex;
			}

			// ID需要录入的时候，下标是一一对应的。
		} else {
			ret = valuesIndex;
		}
		return ret;
	}

	/**
	 * 返回录入字段第一列所对应的数据库字段下标。
	 * 
	 * @param generatorInfo 主键生成策略
	 * @param idSize ID个数
	 * @return 数据库字段下标
	 */
	protected int getColumnFirstIndex(IdGeneratorInfo generatorInfo, int idSize) {
		return getColumnIndex(generatorInfo, idSize, 0);
	}
	
	/**
	 * 取得交换错误文件名字。
	 * 
	 * @param tableName 表名
	 * @param importDate 具体导入时间
	 * @return 文件名
	 */
	protected String getFailureRecordFileName(String tableName, Date importDate) {
		String datePattern = "yyyyMMdd-HHmmssSSS";
		String dateStr = DateUtils.format(datePattern, importDate);
		String name = FAILURERECORD_FILE_PATTERN.replace("{table}", tableName);
		name = name.replace("{date}", dateStr);
		return name;
	}

	/** 
	 * 取得取消标志。
	 * 
	 * @return 取消标志
	 */
	public static boolean isCancelled() {
		return cancelled;
	}

	/** 
	 * 设置取消标志 。
	 * 
	 * @param cancelled 取消标志
	 */
	public static void setCancelled(boolean cancelled) {
		BaseParser.cancelled = cancelled;
	}
}
